package tc.de.krawallmann.gravoblocks.util.pathfinding;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

/**
 * The Class AStar. Adapted from
 * http://www.java-forum.org/codeschnipsel-u-projekte/5046-codeschnipsel.html
 * 
 * @author KrawallMann
 * @version 1.0
 */
public final class AStar {

	public static boolean astar(final boolean[][] field, final int startX, final int startY,
			final int targetX, final int targetY, final int yLargerThan) {
		final List<Point> neighbors = AStar.getNeighbors();
		final List<Node> closedList = new ArrayList<Node>();
		final List<Node> openList = new ArrayList<Node>();
		final Node startNode = new Node(null, 0, 0, startX, startY);
		openList.add(startNode);
		while (!openList.isEmpty()) {
			final Node current = openList.remove(0);
			final List<Node> successors = new ArrayList<Node>();
			final int currentX = current.getXPosition();
			final int currentY = current.getYPosition();
			for (final Point neighborPoint : neighbors) {
				if ((currentX + (int) neighborPoint.getX() >= 0)
						&& (currentX + (int) neighborPoint.getX() < field.length)
						&& (currentY + (int) neighborPoint.getY() >= 0)
						&& (currentY + (int) neighborPoint.getY() < field[0].length)
						&& (currentY + (int) neighborPoint.getY() <= yLargerThan)
						&& field[currentX + (int) neighborPoint.getX()][currentY
								+ (int) neighborPoint.getY()]) {
					final Node newNode =
							new Node(current, currentX + (int) neighborPoint.getX(), currentY
									+ (int) neighborPoint.getY());
					newNode.setGValue(AStar.calculateGValue(newNode));
					newNode.setHValue(AStar.calculateHeuristicsValue(newNode, targetX, targetY));
					newNode.setFValue(newNode.getGValue() + newNode.getHValue());
					successors.add(newNode);
				}
			}
			for (final Node node : successors) {
				if ((node.getXPosition() == targetX) && (node.getYPosition() == targetY)) {
					return true;
				}
				boolean add = true;
				if (AStar.hasBetterNode(node, openList)) {
					add = false;
				}
				if (AStar.hasBetterNode(node, closedList)) {
					add = false;
				}
				if (add) {
					openList.add(node);
				}
			}
			closedList.add(current);

		}
		return false;
	}

	public static List<Point> getNeighbors() {
		final List<Point> ret = new ArrayList<Point>(9);
		for (int alpha = 0; alpha < 360; alpha += 45) {
			final int xPosn = (int) Math.round(Math.cos(Math.toRadians(alpha)));
			final int yPosn = (int) Math.round(Math.sin(Math.toRadians(alpha)));
			ret.add(new Point(xPosn, yPosn));
		}
		return ret;
	}

	private static float calculateGValue(final Node currentNode) {
		final Node newNode = currentNode.getParent();
		return newNode.getGValue() + 1;
	}

	private static float calculateHeuristicsValue(final Node current, final int targetX,
			final int ttargetY) {
		final int deltaX = Math.abs(current.getXPosition() - targetX);
		final int deltaY = Math.abs(current.getYPosition() - ttargetY);
		return (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
	}

	private static boolean hasBetterNode(final Node toCompare, final List<Node> availableNodes) {
		for (final Node node : availableNodes) {
			if ((node.getXPosition() == toCompare.getXPosition())
					&& (node.getYPosition() == toCompare.getYPosition())
					&& (node.getFValue() <= toCompare.getFValue())) {
				return true;
			}
		}
		return false;
	}

	private AStar() {
		// Singleton!
	}
}
